package com.piggymetrics.statistics.service;

import com.gaea.tracker.ServiceTracker;
import com.gaea.tracker.common.Identifier;
import com.google.common.collect.ImmutableMap;
import com.piggymetrics.statistics.client.StatisticsMongoServiceClient;
import com.piggymetrics.statistics.domain.*;
import com.piggymetrics.statistics.domain.Currency;
import com.piggymetrics.statistics.domain.timeseries.DataPoint;
import com.piggymetrics.statistics.domain.timeseries.DataPointId;
import com.piggymetrics.statistics.domain.timeseries.ItemMetric;
import com.piggymetrics.statistics.domain.timeseries.StatisticMetric;
import com.piggymetrics.statistics.tool.NoiseMacker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class StatisticsServiceImpl implements StatisticsService {
	ThreadLocal<ServiceTracker> trackerThreadLocal = new ThreadLocal<ServiceTracker>();


	private final Logger log = LoggerFactory.getLogger(getClass());

	@Autowired
	private ExchangeRatesService ratesService;

	@Autowired
	private StatisticsMongoServiceClient statisticsMongoServiceClient;

	/**
	 * {@inheritDoc}
	 */
	@Override
	public List<DataPoint> findByAccountName(String accountName) {
		if ( trackerThreadLocal.get() == null ){
			trackerThreadLocal.set(ServiceTracker.newInstance("statistics-service"));
		}
		ServiceTracker tracker = trackerThreadLocal.get();
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
						.getRequestAttributes()).getRequest();
		HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder
						.getRequestAttributes()).getResponse();
		tracker.trackInEvent(request, response);

		readInfo();
		Assert.hasLength(accountName);

		NoiseMacker.makeNoise(30);

		Identifier identifier = tracker.generateIdentifier();
		tracker.trackOutEvent(identifier.getCallId());

		List<DataPoint> list = statisticsMongoServiceClient.findByAccountName(accountName,identifier.getUrl(),
						identifier.getServiceName(), identifier.getRequestId(), identifier.getStage(), identifier.getCallId());

		tracker.trackBackInEvent(identifier.getCallId());

		tracker.trackBackOutEvent(response);

		return list;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public String save(String accountName, Account account) {
		if ( trackerThreadLocal.get() == null ){
			trackerThreadLocal.set(ServiceTracker.newInstance("statistics-service"));
		}
		ServiceTracker tracker = trackerThreadLocal.get();
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
						.getRequestAttributes()).getRequest();
		HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder
						.getRequestAttributes()).getResponse();
		tracker.trackInEvent(request, response);

		readInfo();

		NoiseMacker.makeNoise(60);

		Instant instant = LocalDate.now().atStartOfDay()
				.atZone(ZoneId.systemDefault()).toInstant();

		DataPointId pointId = new DataPointId(accountName, Date.from(instant));

		Set<ItemMetric> incomes = account.getIncomes().stream()
				.map(this::createItemMetric)
				.collect(Collectors.toSet());

		Set<ItemMetric> expenses = account.getExpenses().stream()
				.map(this::createItemMetric)
				.collect(Collectors.toSet());

		Map<StatisticMetric, BigDecimal> statistics = createStatisticMetrics(incomes, expenses, account.getSaving());

		DataPoint dataPoint = new DataPoint();
		dataPoint.setId(pointId);
		dataPoint.setIncomes(incomes);
		dataPoint.setExpenses(expenses);
		dataPoint.setStatistics(statistics);
		dataPoint.setRates(ratesService.getCurrentRates());

		log.debug("new datapoint has been created: {}", pointId);

		Identifier identifier = tracker.generateIdentifier();
		tracker.trackOutEvent(identifier.getCallId());

		statisticsMongoServiceClient.save(dataPoint,identifier.getUrl(), identifier.getServiceName(),
						identifier.getRequestId(), identifier.getStage(), identifier.getCallId());

		tracker.trackBackInEvent(identifier.getCallId());

		tracker.trackBackOutEvent(response);
		return "void";
	}

	private Map<StatisticMetric, BigDecimal> createStatisticMetrics(Set<ItemMetric> incomes, Set<ItemMetric> expenses, Saving saving) {

		BigDecimal savingAmount = ratesService.convert(saving.getCurrency(), Currency.getBase(), saving.getAmount());

		BigDecimal expensesAmount = expenses.stream()
				.map(ItemMetric::getAmount)
				.reduce(BigDecimal.ZERO, BigDecimal::add);

		BigDecimal incomesAmount = incomes.stream()
				.map(ItemMetric::getAmount)
				.reduce(BigDecimal.ZERO, BigDecimal::add);

		return ImmutableMap.of(
				StatisticMetric.EXPENSES_AMOUNT, expensesAmount,
				StatisticMetric.INCOMES_AMOUNT, incomesAmount,
				StatisticMetric.SAVING_AMOUNT, savingAmount
		);
	}

	/**
	 * Normalizes given item amount to {@link Currency#getBase()} currency with
	 * {@link TimePeriod#getBase()} time period
	 */
	private ItemMetric createItemMetric(Item item) {

		BigDecimal amount = ratesService
				.convert(item.getCurrency(), Currency.getBase(), item.getAmount())
				.divide(item.getPeriod().getBaseRatio(), 4, RoundingMode.HALF_UP);

		return new ItemMetric(item.getTitle(), amount);
	}

	private void readInfo(){
		try {
			Process process = null;
			List<String> processList = new ArrayList<String>();
			try {
				process = Runtime.getRuntime().exec("uptime");
				BufferedReader input = new BufferedReader(new InputStreamReader(process.getInputStream()));
				String line = "";
				while ((line = input.readLine()) != null) {
					processList.add(line);
				}
				input.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			String[] strings = processList.get(0).split(" ");
			boolean isAverage = false;
			for (String s : strings){
				if ( isAverage ){
					long returnVal = Double.valueOf(s.substring(0, s.length() - 1)).longValue();
					log.warn(returnVal + "");
					Thread.sleep(returnVal * 4);
					break;
				}
				if ( s.length() > 2 && s.substring(0,s.length() - 1).equals("average") ){
					isAverage = true;
				}
			}
		}catch (InterruptedException e){
			log.error(Arrays.toString(e.getStackTrace()));
		}
	}
}
